// This file is part of RedAlert.
//
// RedAlert is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RedAlert is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RedAlert.  If not, see <http://www.gnu.org/licenses/>.
//
// Author: Nicolas Catania (nicolas.catania@gmail.com)

package com.appspot.redalert;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Calendar;
import java.util.GregorianCalendar;


// EventSource is a class to hold the date of the next event.
//
// It uses the preferences storage to persist the date of the next event
// occurence and its period.
//
// Typical Usage:
// ==============
//
//   // Get the instance, initialize it from the prefs DB if applicable.
//   EventSource eventSrc = EventSource.getInstance(context);
//
//   // The event source is valid and initialized.
//   if (!eventSrc.isValid() || !eventSrc.isSet()) {
//     // handle error
//   }
//
//   // Get the number of days left
//   int daysLeft = eventSrc.daysLeftUntilNextOccurence();
//
//   // Update the period and implicitely commit the changes
//   eventSrc.setPeriodInDays(28);
//

public class EventSource {
  private SharedPreferences prefs;
  private int year;
  private int month;
  private int day;
  private int period;
  private Log log;
  private boolean valid;

  private static final String PREFS_NAME = "com.appspot.redalert.prefs";
  private static final String YEAR = "year";
  private static final String MONTH = "month";
  private static final String DAY = "day";
  private static final String PERIOD = "period";
  private static final int DATE_MISSING = -1;
  private static final int DEFAULT_PERIOD = 28;
  private static final int MIN_PERIOD = 10;
  private static final int MAX_PERIOD = 365;
  private static final long MAX_PERIOD_MILLIS = MAX_PERIOD * 24 * 3600 * 1000;
  private static EventSource instance;

  private GregorianCalendar today;
  private GregorianCalendar nextDate;

  // There is only one instance of the event source.
  static EventSource getInstance(Context context) {
    // Only one instance of the prefs exists system wide, however I
    // am not sure how concurrent creation is handled. In doubt I am
    // make sure only one thread does the lazy init of the
    // preferences.
    if (instance == null) {
      synchronized (EventSource.class) {
        if (instance == null) {
          instance = new EventSource();
          instance.init(context);
        }
      }
    }
    return instance;
  }
  
  public int daysLeftUntilNextOccurence() {
    long delta = nextDate.getTimeInMillis() - today.getTimeInMillis();
    
    if (delta < 0) {
      delta = -delta;
    }
    int numDays = (int) (delta / 1000 / 3600 / 24);

    StringBuilder msg = new StringBuilder(90);

    msg.append("Scheduling notification in ").append(numDays).append(" days.");
    log.d(msg.toString());
    return numDays;
  }

  public int period() {
    return period;
  }

  public void setPeriodInDays(int newPeriod) {
    SharedPreferences.Editor edit = prefs.edit();

    edit.putInt(PERIOD, newPeriod);
    edit.commit();
    period = newPeriod;
  }

  // isSet: returns true if the EventSource has been initialized.
  // reset: clear the source and the preferences.
  // isValid: sanity check to make sure the data read from the DB makes sense.
  public boolean isSet() {
    if (year == DATE_MISSING || month == DATE_MISSING || day == DATE_MISSING) {
      log.d("Not initialized yet.");
      return false;
    }
    return true;
  }

  public void reset() {
    SharedPreferences.Editor edit = prefs.edit();

    edit.putInt(YEAR, DATE_MISSING);
    edit.putInt(MONTH, DATE_MISSING);
    edit.putInt(DAY, DATE_MISSING);
    edit.putInt(PERIOD, DEFAULT_PERIOD);
    edit.commit();
  }

  public boolean isValid() {
    if (!isSet()) {
      return true;
    }
    if (period > MAX_PERIOD || period < MIN_PERIOD) {
      StringBuilder msg = new StringBuilder(80);
      msg.append("Period invalid:").append(period);
      log.e(msg.toString());
      return false;        
    }
    if (daysLeftUntilNextOccurence() > MAX_PERIOD) {
      StringBuilder msg = new StringBuilder(80);
      msg.append("Date from prefs too far:").append(nextDate.toString());
      log.e(msg.toString());
      return false;
    }
    return true;
  }

  private EventSource() {
    log = Log.getInstance();
    valid = false;
  }

  private void init(Context context) {
    prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
    today = new GregorianCalendar();

    load();

    if (isValid()) {
      nextDate = new GregorianCalendar(year, month, day);

      // If we missed the last occurence. Fast forward to the next
      // one.
      while (nextDate.before(today)) {
        nextDate.add(Calendar.DAY_OF_YEAR, period);
      }
    }
  }

  private void load() {
    year = prefs.getInt(YEAR, DATE_MISSING);
    month = prefs.getInt(MONTH, DATE_MISSING);
    day = prefs.getInt(DAY, DATE_MISSING);
    period = prefs.getInt(PERIOD, DEFAULT_PERIOD);
  }
}